/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package core;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import logging.LOG;

/**
 *
 * @author Tiago
 */
public abstract class AbstractCoreClient implements Runnable {

    private Selector selector;

    private int timeOut = 500; //todo: fazer timeout de clientes

    private int selectorTimeOut=500;

    private Thread selectorThread;

    private boolean selectorRunning;

    private List<ProtocolSD.Operation> unJoinParam=new LinkedList<ProtocolSD.Operation>();

    private String name="thread";


    public AbstractCoreClient() {
        
    }

    public void addUnJoinParam(ProtocolSD.Operation operation){
        unJoinParam.add(operation);
    }

    public void addUnJoinParam(ProtocolSD.Operation...operation){
        unJoinParam.addAll(Arrays.asList(operation));
    }

    


    public void disconnect() {
        try {
            selectorThread.interrupt();
            selector.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            selectorRunning=false;
            selectorThread = null;
            selector = null;
        }
    }

    public void disconnectKey(SelectionKey key)throws IOException{
        key.cancel();
        key.channel().close();
    }

    public void connect(){
        try {
            runLoop();
        } catch (IOException ex) {
            // TODO
            Logger.getLogger(AbstractCoreServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    protected void runLoop() throws IOException{
        if (selectorRunning) {
            return;
        }

        selector = Selector.open();
        selectorThread = new Thread(this);
        selectorThread.setName(name);
        selectorThread.start();
    }


    
    
    public void run() {
        try {
            this.selectorRunning = true;
            LOG.append("Selector de clientes ligado ");
            while (selector.isOpen() && !selectorThread.isInterrupted()) {
                    int sel = selector.select(selectorTimeOut);
                    if (sel == 0) {
                        continue;
                    }
                    Set<SelectionKey> keySet = selector.selectedKeys();
                    for (SelectionKey key : keySet) {
                        try{
                        if (key.isValid() && key.isConnectable()) {
                            SelectableChannel selChannel = key.channel();
                            if (selChannel instanceof SocketChannel) {
                                SocketChannel socketChannel = (SocketChannel) selChannel;
                                boolean connected = socketChannel.finishConnect();

                                if (connected) {
                                    ProtocolSD proto = new ProtocolSD(createInstance(), key);
                                    proto.setUnJoinParam(unJoinParam);
                                    key.interestOps(SelectionKey.OP_WRITE);     
                                    IServerCallBack serverCallBack = (IServerCallBack)key.attachment();
                                    proto.getListener().setCallBack(serverCallBack);
                                    key.attach(proto);
                                    handleFinishConnect(key);
                                    serverCallBack.onFinishClient(proto);
                                    
                                }
                            }
                        }

                        if (key.isValid() && key.isReadable()) {
                            ((ProtocolSD) key.attachment()).handlerRead();
                        }

                        if (key.isValid() && key.isWritable()) {
                            ((ProtocolSD) key.attachment()).handlerWrite();
                        }
                        }catch(Throwable exception){
                            try{
                            disconnectKey(key);
                            if(key.attachment() instanceof ProtocolSD){
                                ProtocolSD protocolSD = (ProtocolSD) key.attachment();
                                if(protocolSD.getListener().isClose()){
                                    protocolSD.getListener().onAcceptConnect();
                                }else{
                                    handlerFatalErrorCliente(exception);
                                }
                            }else{
                                handlerFatalErrorCliente(exception);
                            }
                            }catch(IOException exception1){
                                handlerFatalErrorCliente(exception1);
                            }
                        }
                    }
                    selector.selectedKeys().clear();
            }
        } catch (Throwable exc) {
            disconnect();
            handlerServerError(exc);
        } finally {
            selectorRunning=false;
        }
    }


    public void setName(String name){
        this.name= name;
    }


    public synchronized void connect(String host, int port,IServerCallBack callback) {

        try {
            runLoop();
            InetSocketAddress address = new InetSocketAddress(host, port);
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(address);
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            socketChannel.keyFor(selector).attach(callback);
            selector.wakeup();

        } catch (Throwable ex) {
            handlerFatalErrorCliente(ex);
        }
    }

    /**
     * Erros de servidor
     *
     * @param exception
     */
    public abstract void handlerServerError(Throwable exception);

    protected abstract ProtocolSD.IProtocolListener createInstance();

    /**
     * Método invocado quando ocorre um erro ao tratar um cliente
     * O cliente já se encontra desconectado após a invocação do método
     *
     */
    public abstract void handlerFatalErrorCliente(Throwable throwable);

    public abstract void handleFinishConnect(SelectionKey key);
}
